/************************************************************************

								Heroes IV
					  Copyright 2001, The 3DO Company

	------------------------------------------------------------------
				       ownable_garrisonable_adv_object.cpp

	$Header: $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "ownable_garrisonable_adv_object.h"

#include "abstract_script_action.h"
#include "adventure_frame.h"
#include "adventure_map.h"
#include "adventure_map_window.h"
#include "adventure_sounds.h"
#include "army.h"
#include "dialog_garrison.h"
#include "enum_operations.h"
#include "music.h"
#include "ownable_event.h"
#include "player.h"
#include "player_color.h"
#include "script_targeting.h"
#include "streambuf_operators.h"

namespace
{

	int const k_current_map_format_version = 2; // should be version from t_editor_ownable_garrisoned_adv_object
	int const k_current_save_format_version = 5;

	// Convert t_ownable_garrisonable_adv_object's save file version to t_owned_adv_object's save file version
	inline int to_owned_version( int version )
	{
		return 0;
	}


} // unnamed namespace

// ---------------------------------------------------------------
// ---------------------------------------------------------------
t_ownable_garrisonable_adv_object::t_ownable_garrisonable_adv_object( std::string const& source )
	:  t_owned_adv_object( source ),
	   m_can_remove_garrison( true )
{
	for ( int i = 0; i < k_garrison_scriptable_event_count; i++ )
	{
		m_built_in_events[i] = t_ownable_built_in_event_ptr( new t_ownable_built_in_event );
	}
}

// --------------------------------------------------------------
// Cannot delete garrisons
// --------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::is_deleted_by_deletion_marker() const
{
	return false;
}

// --------------------------------------------------------------
// Read from map file
// --------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::read_from_map( std::streambuf& buffer, t_progress_handler* )
{
	t_uint16 format_version = get< t_uint16 >( buffer );

	return read_data_from_map( buffer, format_version );
}


bool t_ownable_garrisonable_adv_object::is_triggered_by( t_creature_array const& army ) const
{
	return !army.is_boat();
}

// --------------------------------------------------------------
// Read everything but version from map file. Allows 
// derived classes to read extra data as well
// --------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::read_data_from_map( std::streambuf& buffer, t_uint16 format_version )
{	
	if ( format_version > k_current_map_format_version )
		return false;

	t_player_color new_owner_color;
	if ( format_version >= 1 )
	{
		new_owner_color = t_player_color( get< t_uint8 >( buffer ) );
		if ( new_owner_color < 0 || new_owner_color >= k_player_color_count )
			return false;
	}
	else
		new_owner_color = k_player_gray;

	set_player_color( new_owner_color );

	if ( format_version >= 2 && new_owner_color != k_player_gray )
	{
		m_can_remove_garrison = ( get< t_uint8 >( buffer ) != 0 );
	}
	else
	{
		m_can_remove_garrison = true;
	}

	return    read_built_in_events_from_map( buffer, format_version )
		   && read_continuous_events_from_map( buffer, format_version )
		   && read_timed_events_from_map( buffer, format_version )
		   && read_triggerable_events_from_map( buffer, format_version )
		   && read_garrison_from_map( buffer, format_version );
}

// ------------------------------------------------------------------------
// Read built in events from map file
// ------------------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::read_built_in_events_from_map( std::streambuf& stream, t_uint16 format_version )
{
	if ( format_version < 2 )
		return true;

	int i;

	for ( i = 0; i < k_garrison_scriptable_event_count; i++ )
	{
		if ( !m_built_in_events[i]->read_from_map( stream ) )
			return false;
	}

	return true;
}

// ------------------------------------------------------------------------
// Read continuous events from map file
// ------------------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::read_continuous_events_from_map( std::streambuf& stream, t_uint16 format_version )
{
	m_continuous_events.clear();

	if ( format_version < 2 )
		return true;

	t_uint16 count = get< t_uint16 >( stream );

	m_continuous_events.reserve( count );

	while ( count ) 
	{
		t_ownable_continuous_event_ptr event_ptr( new t_ownable_continuous_event );
		
		if ( !event_ptr->read_from_map( stream ) )
			return false;

		m_continuous_events.push_back( event_ptr );

		count--;
	}

	return true;
}

// ------------------------------------------------------------------------
// Read the initial garrison from map file, or default to empty
// ------------------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::read_garrison_from_map( std::streambuf& stream, t_uint16 format_version )
{
	bool custom_garrison;

	if ( format_version < 2 )
		custom_garrison = false;
	else
		custom_garrison = ( get< t_uint8 >( stream ) != 0 );

	if ( custom_garrison )
	{
		if ( !t_creature_array::read_from_map( stream ) )
			return false;
	}
	else
	{
		t_creature_array::clear();
	}

	return true;
}

// ------------------------------------------------------------------------
// Read the timed events from map file
// ------------------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::read_timed_events_from_map( std::streambuf& stream, t_uint16 format_version )
{
	m_timed_events.clear();

	if ( format_version < 2 )
		return true;

	t_uint16 count = get< t_uint16 >( stream );

	m_timed_events.reserve( count );

	while ( count ) 
	{
		t_ownable_timed_event_ptr event_ptr( new t_ownable_timed_event );
		
		if ( !event_ptr->read_from_map( stream ) )
			return false;

		m_timed_events.push_back( event_ptr );

		count--;
	}

	return true;
}

// ------------------------------------------------------------------------
// Read the triggerable events from map file
// ------------------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::read_triggerable_events_from_map( std::streambuf& stream, t_uint16 format_version )
{
	m_triggerable_events.clear();

	if ( format_version < 2 )
		return true;

	t_uint16 count = get< t_uint16 >( stream );

	m_triggerable_events.reserve( count );

	while ( count ) 
	{
		t_ownable_triggerable_event_ptr event_ptr( new t_ownable_triggerable_event );
		
		if ( !event_ptr->read_from_map( stream ) )
			return false;

		m_triggerable_events.push_back( event_ptr );

		count--;
	}

	return true;
}

bool t_ownable_garrisonable_adv_object::read( std::streambuf&						stream, 
											  t_qualified_adv_object_type const&	type )
{
	int version = get<t_uint16>( stream );

	return read_version( stream, type, version );
}


// --------------------------------------------------------------
// Read from save file
// --------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::read_version( std::streambuf&					  stream, 
													  t_qualified_adv_object_type const&  type,
													  int								  version )
{
	if ( version < 0 || version > k_current_save_format_version )
		return false;

	if ( !t_owned_adv_object::read( stream, type, to_owned_version( version ) ) )
		return false;

	if ( version >= 1 )
	{
		if (version < 4 )
		{
			if ( !t_creature_array::read_version( stream, 0 ) )
				return false;
		}
		else if (version < 5)
		{
			if (!t_creature_array::read_version( stream, 1 ))
				return false;
		}
		else if (!t_creature_array::read( stream )) // versions 5+ have a version in creature array
			return false;
	}

	if ( version >= 2 )
	{
		if ( !read_events( stream, version ) )
		{
			return false;
		}
	}

	if ( version >= 3 )
	{
		m_can_remove_garrison = ( get< t_uint8 >( stream ) != 0 );
	}
	else
	{
		m_can_remove_garrison = true;
	}
	
	return true;
}

// ------------------------------------------------------------------------
// Read all events from save file
// ------------------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::read_events( std::streambuf& stream, int version )
{
	t_ownable_timed_event_list new_timed_events;
	t_ownable_triggerable_event_list new_triggerable_events;
	t_ownable_continuous_event_list new_continuous_events;
	
	if ( version >= 1 )
	{
		int scripting_version = get< t_uint16 >( stream );

		t_garrison_scriptable_event event_id;
		for (	event_id = t_garrison_scriptable_event( 0 );
				event_id < k_garrison_scriptable_event_count;
				enum_incr( event_id ) )
		{
			t_ownable_built_in_event_ptr new_event_ptr( new t_ownable_built_in_event );
			
			if ( !new_event_ptr->read( stream, scripting_version ) )
				return false;

			m_built_in_events[ event_id ] = new_event_ptr;
		}
				
		// Read the timed events
		int count = get< t_uint16 >( stream );
		new_timed_events.reserve( count );

		while ( count > 0 )
		{
			t_ownable_timed_event_ptr new_event_ptr( new t_ownable_timed_event );
			
			if ( !new_event_ptr->read( stream, scripting_version ) )
				return false;

			new_timed_events.push_back( new_event_ptr );

			--count;
		}

		// Read triggerable events
		count = get< t_uint16 >( stream );
		new_triggerable_events.reserve( count );

		while ( count > 0 )
		{
			t_ownable_triggerable_event_ptr new_event_ptr( new t_ownable_triggerable_event );
			if ( !new_event_ptr->read( stream, scripting_version ) )
				return false;

			new_triggerable_events.push_back( new_event_ptr );

			--count;
		}

		// Read continuous events
		count = get< t_uint16 >( stream );
		new_continuous_events.reserve( count );

		while ( count > 0 )
		{
			t_ownable_continuous_event_ptr new_event_ptr( new t_ownable_continuous_event );
			if ( !new_event_ptr->read( stream, scripting_version ) )
				return false;

			new_continuous_events.push_back( new_event_ptr );

			--count;
		}
	}

	m_timed_events.swap( new_timed_events );
	m_triggerable_events.swap( new_triggerable_events );
	m_continuous_events.swap( new_continuous_events );

	return true;
}

// --------------------------------------------------------------
// write to save file
// --------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::write( std::streambuf& stream ) const
{
	put< t_uint16 >( stream, k_current_save_format_version );
	if (   !t_owned_adv_object::write( stream )
		|| !t_creature_array::write( stream ) 
		|| !write_events( stream ) )
	{
		return false;
	}

	put< t_uint8 >( stream, m_can_remove_garrison );

	return true;
}

// ------------------------------------------------------------------------
// Write events to save file
// ------------------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::write_events( std::streambuf& stream ) const
{
	put < t_uint16 >( stream, t_abstract_script_action::get_version() );

	t_garrison_scriptable_event event_id;
	for (	event_id = t_garrison_scriptable_event( 0 );
			event_id < k_garrison_scriptable_event_count;
			enum_incr( event_id ) )
	{
		if ( !m_built_in_events[ event_id ]->write( stream ) )
			return false;
	}
	
	int count = m_timed_events.size();
	put< t_uint16 >( stream, count );
	int i;
	for (i = 0; i < count; i++) {
		if (m_timed_events[i]->write( stream ) == false) 
			return false;
	}

	count = m_triggerable_events.size();
	put< t_uint16 >( stream, count );
	for (i = 0; i < count; i++) {
		if (m_triggerable_events[i]->write( stream ) == false) 
			return false;
	}

	count = m_continuous_events.size();
	put< t_uint16 >( stream, count );
	for (i = 0; i < count; i++) {
		if (m_continuous_events[i]->write( stream ) == false) 
			return false;
	}

	return true;
}

// -------------------------------------------------------------------
// Pass various notifications to the garrison
// -------------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::preplacement( t_adventure_map& map, int pass )
{
	bool result_base = t_owned_adv_object::preplacement( map, pass );

	t_player *owner = map.get_player( get_player_color() ); 
	bool result_garrison = t_creature_array::preplacement( map, pass, owner );

	return result_base || result_garrison;
}

// ---------------------------------------------------------------
// ---------------------------------------------------------------
void t_ownable_garrisonable_adv_object::read_postplacement( t_adventure_map& map )
{
	t_owned_adv_object::read_postplacement( map );
	t_creature_array::read_postplacement( map );
}

// ---------------------------------------------------------------
// ---------------------------------------------------------------
void t_ownable_garrisonable_adv_object::process_new_day()
{
	t_creature_array::process_new_day( get_owner_number() < 0 );
	t_owned_adv_object::process_new_day();
}

// -----------------------------------------------------------
// keep ownership info in player up to date
// -----------------------------------------------------------
void t_ownable_garrisonable_adv_object::set_owner( int player_number )
{
	if (get_owner() != 0)
		get_owner()->remove_garrison( this );

	t_owned_adv_object::set_owner( player_number );
	
	if (get_owner() != 0)
		get_owner()->add_garrison( this );
}

// -----------------------------------------------------------
// keep ownership info in player up to date
// -----------------------------------------------------------
void t_ownable_garrisonable_adv_object::initialize( t_adventure_map& map )
{
	t_owned_adv_object::initialize( map );
	t_creature_array::initialize();

	if ( get_owner_number() >= 0 )
	{
		get_owner()->add_garrison( this );
	}
}

// -----------------------------------------------------------
// keep ownership info in player up to date
// -----------------------------------------------------------
void t_ownable_garrisonable_adv_object::destroy()
{
	if (get_owner_number() >= 0)
		get_owner()->remove_garrison( this );

	get_map()->remove( this );

	t_owned_adv_object::destroy();
}

// -----------------------------------------------------------
// keep ownership info in player up to date
// -----------------------------------------------------------
void t_ownable_garrisonable_adv_object::place( t_adventure_map& map, t_adv_map_point const& point )
{
	t_owned_adv_object::place( map, point );

	if (get_owner_number() >= 0)
		get_owner()->add_garrison( this );

	map.add( this );
}

// -----------------------------------------------------------
// -----------------------------------------------------------
int t_ownable_garrisonable_adv_object::compute_scouting_range() const
{
	int base_range = t_owned_adv_object::compute_scouting_range();
	int garrison_range = t_creature_array::compute_scouting_range();
	if ( garrison_range > base_range )
		return garrison_range;
	return base_range;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
void t_ownable_garrisonable_adv_object::execute_script(t_garrison_scriptable_event event_id, t_army* army)
{
	assert( event_id >= 0 && event_id < k_garrison_scriptable_event_count );
	assert( army != 0 );

	t_ownable_built_in_event_ptr & event_ptr = m_built_in_events[ event_id ];

	if (!event_ptr->ownership_test(get_owner())) return;

	t_adventure_map_ptr map = get_map();

	t_adventure_frame * frame = get_adventure_frame();
	event_ptr->execute_effects(map->get_current_player(), frame);

	if ( map->is_game_over() )
		return;

	frame->update();

	t_ownable_built_in_event::t_script const & script = event_ptr->get_script();
	script.execute( t_script_context_town( map, NULL, this, army, army->get_owner(), &army->get_position() ) );
	if( map->is_game_over() )
		return;

	frame->update();
	post_execute_validate(this);
	post_execute_validate(army);
		
	if ( script.pending_removal() ) {
		script.clear_removal();
		event_ptr = new t_ownable_built_in_event;
	}

	map->process_continuous_events( frame );

	if ( map->is_game_over() )
		return;

	frame->update();
}

// -----------------------------------------------------------
// -----------------------------------------------------------
bool t_ownable_garrisonable_adv_object::process_timed_events( t_adventure_map& map, bool start_of_day )
{
	bool result = false;

	int size = m_timed_events.size();
	for (int i = 0; i < size; i++) {
		t_ownable_timed_event_ptr& event = m_timed_events[i];
		if ( event->is_active_on_day(map.get_day()) 
			&& (&map.get_current_player() == get_owner())
			&& event->ownership_test(get_owner())
			&& (start_of_day || !event->has_executed()) )
		{
			result = true;

			event->set_execution_flag();
			event->execute_effects(map.get_current_player(), get_adventure_frame());
			get_adventure_frame()->update();

			t_timed_event::t_script const & script = event->get_script();
			script.execute(t_script_context_town(&map, this, this, NULL, NULL, &get_position()));
			if( map.is_game_over() )
				return false;

			get_adventure_frame()->update();
			post_execute_validate(this);

			if (script.pending_removal()) {
				script.clear_removal();
				event = new t_ownable_timed_event;
			}
		}
		else
			if (start_of_day)
				event->clear_execution_flag();
	}

	return result;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
bool t_ownable_garrisonable_adv_object::process_continuous_events( t_adventure_map& map )
{
	bool result = false;

	int size = m_continuous_events.size();
	for (int i = 0; i < size; i++) {
		t_ownable_continuous_event_ptr & event = m_continuous_events[i];
		if (( !event->get_run_only_during_owners_turn() || &map.get_current_player() == get_owner() )
			&& event->ownership_test(get_owner()) )
		{
			result = true;

			t_continuous_event::t_script const & script = event->get_script();
			script.execute(t_script_context_town(&map, this, this, NULL, NULL, &get_position()));
			if( map.is_game_over() )
				return false;

			get_adventure_frame()->update();
			post_execute_validate(this);

			if (script.pending_removal()) {
				script.clear_removal();
				event = new t_ownable_continuous_event;
			}
		}
	}

	return result;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
bool t_ownable_garrisonable_adv_object::process_triggerable_events( t_adventure_map& map, std::string const& name )
{
	bool result = false;

	int size = m_triggerable_events.size();
	for (int i = 0; i < size; i++) {
		t_ownable_triggerable_event_ptr & event = m_triggerable_events[i];
		if (( string_insensitive_compare(event->get_name(), name) == 0 )
			&& event->ownership_test(get_owner()) )
		{
			result = true;

			event->execute_effects(map.get_current_player(), get_adventure_frame());
			get_adventure_frame()->update();
			
			t_triggerable_event::t_script const & script = event->get_script();
			script.execute(t_script_context_town(&map, this, this, NULL, NULL, &get_position()));
			if( map.is_game_over() )
				return false;

			get_adventure_frame()->update();
			post_execute_validate(this);

			if (script.pending_removal()) {
				script.clear_removal();
				event = new t_ownable_triggerable_event;
			}
		}
	}

	return result;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
void t_ownable_garrisonable_adv_object::copy_events( t_ownable_built_in_event_ptr built_in[ k_garrison_scriptable_event_count ],
											 t_ownable_continuous_event_list const& continuous,
											 t_ownable_timed_event_list const& timed,
											 t_ownable_triggerable_event_list const& triggerable)
{
	for (int i = 0; i < k_garrison_scriptable_event_count; i++) 
	{
		m_built_in_events[i] = built_in[i];
	}
	m_continuous_events = continuous;
	m_timed_events = timed;
	m_triggerable_events = triggerable;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
t_ownable_garrisonable_adv_object* t_ownable_garrisonable_adv_object::get_ownable_garrison()
{
	return this;
}

// -----------------------------------------------------------
// -----------------------------------------------------------
bool t_ownable_garrisonable_adv_object::get_virtual_position( t_adv_map_point& position ) const
{
	if ( find_adjacent_space( position, get_garrison() ) )
	{
		position.on_bridge = false;
		return true;
	}
	return false;
}

// ---------------------------------------------------------------
// ---------------------------------------------------------------
void t_ownable_garrisonable_adv_object::on_begin_turn()
{
	t_creature_array::on_begin_turn();
	t_owned_adv_object::on_begin_turn();
}

// ---------------------------------------------------------------
// ---------------------------------------------------------------
void t_ownable_garrisonable_adv_object::on_end_turn()
{
	t_creature_array::on_end_turn();
	t_owned_adv_object::on_end_turn();
}

// ---------------------------------------------------------------
// ---------------------------------------------------------------
t_skill_mastery t_ownable_garrisonable_adv_object::get_anti_stealth_level() const
{
	return t_creature_array::get_anti_stealth_level();
}

// ---------------------------------------------------------------
// Run the garrison dialog
// ---------------------------------------------------------------
void t_ownable_garrisonable_adv_object::run_garrison_dialog( t_army * visiting_army )
{
	std::string intro_text;
	if ( visiting_army == NULL )
	{
		assert( get_map()->get_player_number() == get_owner_number() );

		if ( can_remove_garrison() )
		{
			intro_text = get_text("view_garrison");
		}
		else
		{
			intro_text = get_text("view_garrison_no_remove");
		}
	}
	else if ( visiting_army->get_owner_number() == get_owner_number() )
	{
		if ( can_remove_garrison() )
		{
			intro_text = get_text("visit_garrison");
		}
		else
		{
			intro_text = get_text("visit_garrison_no_remove");
		}
	}
	else
	{
		if ( can_remove_garrison() )
		{
			intro_text = get_text("visit_garrison_ally");
		}
		else
		{
			intro_text = get_text("visit_garrison_no_remove_ally");
		}
	}

	t_counted_ptr<t_dialog_garrison> dialog_ptr;

	dialog_ptr = new t_dialog_garrison( get_adventure_frame() );
	dialog_ptr->init_dialog( visiting_army, this, get_adventure_frame(), get_text ("Name"), intro_text );
	t_sound_cache music = get_music_playing();
	stop_music();
	get_dialog_sound( k_dialog_sound_military )->play( get_sound_volume() );
	dialog_ptr->run_modal();
	play_music( music );
}

// ---------------------------------------------------------------
// If there were heroes in the garrison, and they lost, 
// put them in a graveyard
// ---------------------------------------------------------------
void t_ownable_garrisonable_adv_object::place_gravestone()
{
	assert( !empty() );

	t_adventure_map & map = *get_map();

	t_army_ptr gravestone = new t_army( this );
	gravestone->set_owner( get_owner_number() );

	bool found_square = false;
	t_adv_map_point result;

	t_adv_map_point test_point;

	if ( get_trigger_cell( test_point ) )
	{
		// Check square under garrison
		if ( map.is_open( test_point, *gravestone ) )
		{
			result = test_point;
			found_square = true;
		}
	}

	if ( !found_square )
	{
		if ( find_adjacent_space( test_point, *gravestone ) )
		{
			found_square = true;
			result = test_point;
		}
	}

	if ( !found_square )
	{
		if ( find_nearby_space( test_point, *gravestone ) )
		{
			found_square = true;
			result = test_point;
		}
	}

	assert( found_square ); // Or how the heck is the garrison being attacked!

	// Final hack fallback
	if ( !found_square )
	{
		result = get_position();
	}


	gravestone->place( map, result );
}

// ---------------------------------------------------------------
// center on this object
// ---------------------------------------------------------------
bool t_ownable_garrisonable_adv_object::select()
{
	t_adventure_map* map = get_map();

	if (map == 0 || !is_on_map())
		return false;

	t_adventure_map_window* window = map->get_map_window();

	if (window == 0)
		return false;

	window->center_view( get_position() );
	return true;
}
